package com.mysite.angular.common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;

import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

/**
 * 分页的拦截器
 * 
 * @author congg
 * @since 2017年6月13日 下午3:24:16
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) })
@SuppressWarnings({ "rawtypes", "unchecked" })
public class PageInterceptor implements Interceptor {
	
	public Object intercept(Invocation invocation) throws Throwable {

		RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget();
		BaseStatementHandler delegate = (BaseStatementHandler) ReflectUtil.getFieldValue(statementHandler, "delegate");
		MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
		
		// 不是查询的sql，直接进入下一个请求
		if(!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
			return invocation.proceed();
		}
		
		BoundSql boundSql = delegate.getBoundSql();
		Object parameterObject = boundSql.getParameterObject();
		
		// 参数不是 java.util.Map 的实现类，直接进入下一个请求
		if(!(parameterObject instanceof Map)) {
			return invocation.proceed();
		}
		
		Map searchMap = (Map) parameterObject;
		
		 // 没有包含分页必须的参数，直接进入下一个请求
		if (!searchMap.containsKey("currentPage") || !searchMap.containsKey("showRecord")) {
			return invocation.proceed();
		}
		
		int count = 0;
		int length = Integer.parseInt(searchMap.get("showRecord").toString());
		int start = (Integer.parseInt(searchMap.get("currentPage").toString()) - 1) * length;
		String sortName = (String) searchMap.get("sortName");
		String sortOrder = (String) searchMap.get("sortOrder");
		
		// 获取分页记录数的sql
		String selectId = mappedStatement.getId() + "Count";
		MappedStatement ms = null;
		BoundSql bsql = null;
		String countSql = null;
		try {
			ms = mappedStatement.getConfiguration().getMappedStatement(selectId);
			bsql = ms.getBoundSql(parameterObject);
			countSql = bsql.getSql();
		} catch (Exception e) {
			System.out.println("Can not find the SQL of ID：" + selectId);
		}
		
		// 分页记录数的sql不为 null，则 查询分页记录数
		if(countSql != null){
			Connection connection = (Connection) invocation.getArgs()[0];
			PreparedStatement countStmt = connection.prepareStatement(countSql);
			setParameters(countStmt, ms, bsql, parameterObject);
			ResultSet rs = countStmt.executeQuery();
			if (rs.next()) {
				count = rs.getInt(1);
			}
			rs.close();
			countStmt.close();
		}
		
		// 把总记录数设回查询的参数里
		searchMap.put("totalRecord", count);
		
		// 如果查询到的总记录数为0， 为了提高查询效率， 设置boundSql 为简单的sql
		if(count == 0){
			ReflectUtil.setFieldValue(boundSql, "sql", "select 1 from (select 1) temp where 1 = 0");
			ReflectUtil.setFieldValue(boundSql, "parameterMappings", null);
			return invocation.proceed();
		}

		// 查询分页的list
		String sql = boundSql.getSql();
		StringBuffer pageSql = new StringBuffer(sql);
		if (sortName != null) {
			pageSql.append(" order by " + sortName);
			if (sortOrder != null) {
				pageSql.append(" " + sortOrder);
			}
		}
		pageSql.append(" limit " + start + ", " + length);
		ReflectUtil.setFieldValue(boundSql, "sql", pageSql.toString());

		return invocation.proceed();
	}

	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	public void setProperties(Properties properties) {

	}

	/**
	 * 对SQL参数(?)设值,参考org.apache.ibatis.executor.parameter.DefaultParameterHandler
	 * 
	 * @author congg
	 * @since 2017年6月13日 下午3:24:16
	 */
	private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
			Object parameterObject) throws Exception {
		ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		if (parameterMappings != null) {
			Configuration configuration = mappedStatement.getConfiguration();
			TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
			MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
			for (int i = 0; i < parameterMappings.size(); i++) {
				ParameterMapping parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					Object value;
					String propertyName = parameterMapping.getProperty();
					PropertyTokenizer prop = new PropertyTokenizer(propertyName);
					if (parameterObject == null) {
						value = null;
					} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)
							&& boundSql.hasAdditionalParameter(prop.getName())) {
						value = boundSql.getAdditionalParameter(prop.getName());
						if (value != null) {
							value = configuration.newMetaObject(value)
									.getValue(propertyName.substring(prop.getName().length()));
						}
					} else {
						value = metaObject == null ? null : metaObject.getValue(propertyName);
					}
					TypeHandler typeHandler = parameterMapping.getTypeHandler();
					if (typeHandler == null) {
						throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName
								+ " of statement " + mappedStatement.getId());
					}
					typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
				}
			}
		}
	}
}
